//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<expose_decl_python.h>
///	@path	~/src/python/wrap/
///	@date	2008/09/16
///	@brief	.

#pragma once

#include <boost/any.hpp>
#include <boost/shared_ptr.hpp>

#include "python/python_plug.h"
#include "python/wrap/expose_python.h"
#include "boost/python/converter/builtin_converters.hpp"

using boost::shared_ptr;
using boost::noncopyable;
using boost::python::object;
using boost::python::handle;
using boost::python::def;
using boost::python::enum_;
using boost::python::class_;
using boost::python::init;
using boost::python::no_init;
using boost::python::self;
using boost::python::bases;
using boost::python::return_internal_reference;
using boost::python::with_custodian_and_ward;
using boost::python::return_value_policy;
using boost::python::copy_const_reference;
using boost::python::copy_non_const_reference;
using boost::python::reference_existing_object;
using boost::python::manage_new_object;
using boost::python::return_by_value;

using boost::python::extract;
using boost::python::error_already_set;

#define RET_COPYREF		return_value_policy<copy_const_reference>() 

using boost::python::make_constructor;
using boost::python::make_function;
using boost::python::make_tuple;
using boost::python::make_getter;
using boost::python::make_setter;

using boost::python::register_ptr_to_python;

namespace boost{ namespace python{

namespace converter {

inline PyObject* value_to_python(boost::any x)
{
	if(x.type() == typeid(int))
		return PyInt_FromLong(any_cast<int>(x));

	if(x.type() == typeid(float))
		return PyFloat_FromDouble(any_cast<float>(x));

	if(x.type() == typeid(double))
		return PyFloat_FromDouble(any_cast<double>(x));

	if(x.type() == typeid(std::string))
	{
		std::string str = any_cast<std::string>(x);
		return PyString_FromStringAndSize(str.data(),str.size());
	}

	if(x.type() == typeid(std::wstring))
	{
		std::string str;
		xeres::wcs_to_mbs( any_cast<std::wstring>(x).c_str() , str );
		return PyString_FromStringAndSize(str.data(),str.size());
	}
	return PyInt_FromLong(0);
}

} // converter

template <> struct to_python_value<boost::any&> : detail::builtin_to_python
{
	inline PyObject* operator()(boost::any const& x) const
	{
		return (converter::value_to_python(x));
	}
	inline PyTypeObject const* get_pytype() const
	{
		return NULL;
	}
};
template <> struct to_python_value<boost::any const&> : detail::builtin_to_python
{
	inline PyObject* operator()(boost::any const& x) const
	{
		return (converter::value_to_python(x));
	}
	inline PyTypeObject const* get_pytype() const
	{
		return NULL;
	}
};

namespace converter
{
	template <> struct arg_to_python< boost::any > : handle<>
	{
		arg_to_python(boost::any const& x) : python::handle<>(value_to_python(x)) {}
	};
} 

struct from_python_any
{
	from_python_any()
	{
		boost::python::converter::registry::push_back(
			&convertible,
			&construct,
			boost::python::type_id<boost::any>());
	}

	static void* convertible(PyObject* obj_ptr)
	{
		if( !check_convertible( obj_ptr ) )
			return NULL;
		return obj_ptr;
	}

	static bool check_convertible( PyObject * obj_ptr )
	{
		if( !(
			PyInt_Check(obj_ptr) ||
			PyFloat_Check(obj_ptr) ||
			PyString_Check(obj_ptr) ||
			PyUnicode_Check(obj_ptr) ) )
			return false;
		return true;
	}

	static void construct(
		PyObject* obj_ptr,
		boost::python::converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = (
			(boost::python::converter::rvalue_from_python_storage<boost::any>*)
			data)->storage.bytes;
		new (storage) boost::any();
		data->convertible = storage;
		boost::any& result = *((boost::any*)storage);

		if( PyInt_Check(obj_ptr) )
			result = PyInt_AS_LONG( obj_ptr );
		else if( PyFloat_Check(obj_ptr) )
			result = PyFloat_AS_DOUBLE( obj_ptr );
		else if( PyString_Check(obj_ptr) )
		{
			std::wstring str;
			xeres::mbs_to_wcs( PyString_AS_STRING( obj_ptr ) , str );
			result = str;
		}
		else if( PyUnicode_Check(obj_ptr) )
		{
			size_t size = PyUnicode_GET_SIZE(obj_ptr) + 1;
			wchar_t * buf = (wchar_t*)malloc( size * sizeof(wchar_t) );
			size = PyUnicode_AsWideChar( (PyUnicodeObject*)obj_ptr , buf , size );
			buf[size] = 0;
			result = std::wstring( buf );
			free( buf );
		}
	}
};

namespace detail{
    
    struct make_value_holder
    {
        template <class T>
        static PyObject* execute(T* p)
        {
            if (p == 0)
            {
                return python::detail::none();
            }
            else
            {
                object p_value( *p );
                return incref( p_value.ptr() );
            }
        }
    };

    template <class R>
    struct reference_existing_object_requires_a_pointer_return_type
    # if defined(__GNUC__) && __GNUC__ >= 3 || defined(__EDG__)
    {}
    # endif
    ;

} //detail


struct return_pointee_value
{
    template <class T>
    struct apply
    {
        BOOST_STATIC_CONSTANT( bool, ok = is_pointer<T>::value );
        
        typedef typename mpl::if_c<
            ok
            , to_python_indirect<T, detail::make_value_holder>
            , detail::reference_existing_object_requires_a_pointer_return_type<T>
        >::type type;
    };
};

inline object pass_through( object const& o ) { return o; }

template< typename _TypeIter > struct iterator_wrappers
{
// 	static object next( _TypeIter * o )
// 	{
// 		object result = o->next();
// 		if( result == handle<>( Py_None ) )
// 		{
// 			
// 		}
// 		return result;
// 	}
	static void wrap( const char* python_name )
	{
		class_<_TypeIter>( python_name , no_init )
			.def( "next" , &_TypeIter::next )
			.def( "__iter__" , &pass_through )
			;
	}
};

template <typename ContainerType>
struct to_tuple
{
	static PyObject* convert(ContainerType const& a)
	{
		boost::python::list result;
		typedef typename ContainerType::const_iterator const_iter;
		for(const_iter p=a.begin();p!=a.end();p++) {
			result.append(boost::python::object(*p));
		}
		return boost::python::incref(boost::python::tuple(result).ptr());
	}

	static const PyTypeObject* get_pytype() { return &PyTuple_Type; }
};

template <typename ContainerType>
struct to_list
{
	static PyObject* convert(ContainerType const& a)
	{
		boost::python::list result;
		typedef typename ContainerType::const_iterator const_iter;
		for(const_iter p=a.begin();p!=a.end();p++) {
			result.append(boost::python::object(*p));
		}
		return boost::python::incref(result.ptr());
	}

	static const PyTypeObject* get_pytype() { return &PyList_Type; }
};

struct default_policy
{
	static bool check_convertibility_per_element() { return false; }

	template <typename ContainerType>
	static bool check_size(boost::type<ContainerType>, std::size_t /*sz*/)
	{
		return true;
	}

	template <typename ContainerType>
	static void assert_size(boost::type<ContainerType>, std::size_t /*sz*/) {}

	template <typename ContainerType>
	static void reserve(ContainerType& a, std::size_t sz) {}
};

struct fixed_size_policy
{
	static bool check_convertibility_per_element() { return true; }

	template <typename ContainerType>
	static bool check_size(boost::type<ContainerType>, std::size_t sz)
	{
		return ContainerType::size() == sz;
	}

	template <typename ContainerType>
	static void assert_size(boost::type<ContainerType>, std::size_t sz)
	{
		if (!check_size(boost::type<ContainerType>(), sz)) {
			PyErr_SetString(PyExc_RuntimeError,
				"Insufficient elements for fixed-size array.");
			boost::python::throw_error_already_set();
		}
	}

	template <typename ContainerType>
	static void reserve(ContainerType& /*a*/, std::size_t sz)
	{
		if (sz > ContainerType::size()) {
			PyErr_SetString(PyExc_RuntimeError,
				"Too many elements for fixed-size array.");
			boost::python::throw_error_already_set();
		}
	}

	template <typename ContainerType, typename ValueType>
	static void set_value(ContainerType& a, std::size_t i, ValueType const& v)
	{
		reserve(a, i+1);
		a[i] = v;
	}
};

struct variable_capacity_policy : default_policy
{
	template <typename ContainerType>
	static void reserve(ContainerType& a, std::size_t sz)
	{
		a.reserve(sz);
	}

	template <typename ContainerType, typename ValueType>
	static void set_value(
		ContainerType& a,
		std::size_t
#if !defined(NDEBUG)
		i
#endif
		,
		ValueType const& v)
	{
		assert(a.size() == i);
		a.push_back(v);
	}
};

struct fixed_capacity_policy : variable_capacity_policy
{
	template <typename ContainerType>
	static bool check_size(boost::type<ContainerType>, std::size_t sz)
	{
		return ContainerType::max_size() >= sz;
	}
};

struct linked_list_policy : default_policy
{
	template <typename ContainerType, typename ValueType>
	static void
		set_value(ContainerType& a, std::size_t /*i*/, ValueType const& v)
	{
		a.push_back(v);
	}
};

struct set_policy : default_policy
{
	template <typename ContainerType, typename ValueType>
	static void
		set_value(ContainerType& a, std::size_t /*i*/, ValueType const& v)
	{
		a.insert(v);
	}
};

template <typename ContainerType, typename ConversionPolicy>
struct from_python_sequence
{
	typedef typename ContainerType::value_type container_element_type;

	from_python_sequence()
	{
		boost::python::converter::registry::push_back(
			&convertible,
			&construct,
			boost::python::type_id<ContainerType>());
	}

	static void* convertible(PyObject* obj_ptr)
	{
		if (!(   PyList_Check(obj_ptr)
			|| PyTuple_Check(obj_ptr)
			|| PyIter_Check(obj_ptr)
			|| PyRange_Check(obj_ptr)
			|| (   !PyString_Check(obj_ptr)
			&& !PyUnicode_Check(obj_ptr)
			&& (   obj_ptr->ob_type == 0
			|| obj_ptr->ob_type->ob_type == 0
			|| obj_ptr->ob_type->ob_type->tp_name == 0
			|| std::strcmp(
			obj_ptr->ob_type->ob_type->tp_name,
			"Boost.Python.class") != 0)
			&& PyObject_HasAttrString(obj_ptr, "__len__")
			&& PyObject_HasAttrString(obj_ptr, "__getitem__")))) return 0;
		boost::python::handle<> obj_iter(
			boost::python::allow_null(PyObject_GetIter(obj_ptr)));
		if (!obj_iter.get()) { // must be convertible to an iterator
			PyErr_Clear();
			return 0;
		}
		if (ConversionPolicy::check_convertibility_per_element()) {
			size_t obj_size = PyObject_Length(obj_ptr);
			if (obj_size < 0) { // must be a measurable sequence
				PyErr_Clear();
				return 0;
			}
			if (!ConversionPolicy::check_size(
				boost::type<ContainerType>(), obj_size)) return 0;
			bool is_range = PyRange_Check(obj_ptr);
			std::size_t i=0;
			if (!all_elements_convertible(obj_iter, is_range, i)) return 0;
			if (!is_range) assert(i == obj_size);
		}
		return obj_ptr;
	}

	// This loop factored out by Achim Domma to avoid Visual C++
	// Internal Compiler Error.
	static bool
		all_elements_convertible(
		boost::python::handle<>& obj_iter,
		bool is_range,
		std::size_t& i)
	{
		for(;;i++) {
			boost::python::handle<> py_elem_hdl(
				boost::python::allow_null(PyIter_Next(obj_iter.get())));
			if (PyErr_Occurred()) {
				PyErr_Clear();
				return false;
			}
			if (!py_elem_hdl.get()) break; // end of iteration
			boost::python::object py_elem_obj(py_elem_hdl);
			boost::python::extract<container_element_type>
				elem_proxy(py_elem_obj);
			if (!elem_proxy.check()) return false;
			if (is_range) break; // in a range all elements are of the same type
		}
		return true;
	}

	static void construct(
		PyObject* obj_ptr,
		boost::python::converter::rvalue_from_python_stage1_data* data)
	{
		boost::python::handle<> obj_iter(PyObject_GetIter(obj_ptr));
		void* storage = (
			(boost::python::converter::rvalue_from_python_storage<ContainerType>*)
			data)->storage.bytes;
		new (storage) ContainerType();
		data->convertible = storage;
		ContainerType& result = *((ContainerType*)storage);
		std::size_t i=0;
		for(;;i++) {
			boost::python::handle<> py_elem_hdl(
				boost::python::allow_null(PyIter_Next(obj_iter.get())));
			if (PyErr_Occurred()) boost::python::throw_error_already_set();
			if (!py_elem_hdl.get()) break; // end of iteration
			boost::python::object py_elem_obj(py_elem_hdl);
			boost::python::extract<container_element_type> elem_proxy(py_elem_obj);
			ConversionPolicy::set_value(result, i, elem_proxy());
		}
		ConversionPolicy::assert_size(boost::type<ContainerType>(), i);
	}
};

} } //boost::python

using boost::python::return_pointee_value;
using boost::python::to_list;
using boost::python::to_tuple;
using boost::python::to_python_converter;
using boost::python::from_python_sequence;
using boost::python::variable_capacity_policy;
using boost::python::default_policy;
using boost::python::fixed_size_policy;
using boost::python::fixed_capacity_policy;
using boost::python::set_policy;
using boost::python::linked_list_policy;

namespace boost { namespace python {
	template <class T> struct pointee< xeres::Ref<T> >
	{
		typedef T type;
	};
	template<class T> inline T * get_pointer( xeres::Ref<T> const & p )
	{
		return const_cast<T*>( p.operator->() );
	}
	template<typename _Ty> class eternal_ptr
	{
	public:
		eternal_ptr(): px(NULL) {}
		eternal_ptr( _Ty * p ) : px(p) {}
		eternal_ptr( const eternal_ptr<_Ty>& rhs ) : px( rhs.px ) {}
		_Ty& operator* () const { return *px; }
		_Ty * operator-> () const { return px; }
		_Ty * operator-> () { return px; }
		bool operator! () const { !px }
	private:
		_Ty * px;
	};
	template <class T> struct pointee< eternal_ptr<T> >
	{
		typedef T type;
	};
	template<class T> inline T * get_pointer( eternal_ptr<T> const & p )
	{
		return const_cast<T*>( p.operator->() );
	}
} }

using boost::python::eternal_ptr;
using boost::python::converter::registration;

namespace xeres {

	template<
		typename _Type
	>
	void register_object_to_python( void )
	{
		ClassID id = _Type::StaticClassID();
		const registration * reg = &boost::python::converter::registered< Ref<_Type> >::converters;
		register_object_to_python( id , reg );
	}

	/*!
		\brief		Register python converter to IObject.
	*/
	void register_object_to_python( ClassID id , const registration * reg );

	/*! 
		\brief		Get python converters.
	*/
	const registration& get_python_converters( ClassID id );

} // namespace xeres


namespace boost{ namespace python{

namespace converter {

	template< typename _IObjectClass >
	struct arg_to_python< xeres::Ref<_IObjectClass> > : handle<>
	{
		arg_to_python( xeres::Ref<_IObjectClass> const& x )
			: handle<>( get_python_converters( x->GetClassID() ).to_python( &x ) )
		{
		}
	};
}

namespace detail {

	template< typename _IObjectClass >
	struct registry_to_python_value< const xeres::Ref<_IObjectClass>& >
	{
		typedef typename value_arg<xeres::Ref<_IObjectClass>>::type argument_type;
		PyObject* operator()( argument_type x ) const
		{
			return get_python_converters( x->GetClassID() ).to_python( &x );
		}
	};
}
}}
